moduleを再利用して、環境ごとに異なる値を設定するTerraformアーキテクチャの一例

moduleを再利用して、環境ごとに異なる値を設定するTerraformアーキテクチャの一例

Clock Icon2024.07.11

環境ごとに設定を変えたい

おのやんです。

みなさん、環境ごとに設定を変えて、Terraform経由でAWSリソースをデプロイしたいと思ったことはありませんか?私はあります。

例えば、開発(dev)、ステージング(stg)、本番(prd)と環境が分かれているとします。この環境それぞれに対して、ほぼ同じ構成で名前だけ変えてデプロイする流れをイメージします。その場合、moduleを共通化するディレクトリ構造を採用して、Terraformの変数を経由して環境ごとに設定を変えてデプロイすることがあります。

この構成のTerraformコードを作成する機会がありましたので、今回はそちらを紹介したいと思います。

ディレクトリ構成

具体的なディレクトリ構成がこちらになります。プロダクトのルートディレクトリにはenvironmentsmodulesの2つのディレクトリを置いています。environmentsにはprod(本番)環境のディレクトリやtest(検証)環境のディレクトリを配置しています。またmodulesにはAmazon VPC(以下、VPC)やAmazon ECS(以下、ECS)など、管理するAWSリソースのコードを配置します。

root
├── environments
│   ├── prod
│   │   ├── main.tf # 各moduleを呼び出し、具体的な設定値を注入
│   │   └── variables.tf # 具体的な設定値を定義
│   └── test
│       ├── main.tf
│       └── variables.tf
└── modules
    ├── vpc
    │   ├── main.tf # 各環境で共通なリソース設定
    │   ├── variables.tf # environmentsから設定値を受け取るための空の変数を定義
    │   └── outputs.tf # 別moduleで必要な値をoutput
    └── ecs
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

moduleの内容

各ディレクトリは、それぞれmain.tfvariables.tfoutputs.tfの3ファイルで構成されています。このうち、modules内のvpc配下のvariables.tfは以下の通りです。environmentディレクトリからの変数を受け取るための、空の変数を設定しています。

modules/vpc/varibles.tf
variable "system_name" {
  type = string
}

variable "environment" {
  type = string
}

variable "vpc_cidr" {
  type = string
}

variable "private_subnet_cidr" {
  type = string
}

modules内のvpc配下のmain.tfは以下の通りです。呼び出し元から受け取る予定の変数を使って、リソース名を設定しています。そのためvariablesの部分には、具体的な値はまだ設定されていません。

modules/vpc/main.tf
#------------------#
# VPC
#------------------#

resource "aws_vpc" "vpc" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "${var.system_name}-${var.environment}-vpc"
  }
}

#------------------#
# Private subnet
#------------------#

resource "aws_subnet" "private_subnet" {
  vpc_id     = aws_vpc.main.id
  cidr_block = var.private_subnet_cidr

  tags = {
    Name = "${var.system_name}-${var.environment}-private-subnet"
  }
}

modules内のvpc配下のoutputs.tfは、以下のようになります。ここでは、VPCのプライベートサブネット情報がECS側のmoduleで必要になるので、outputに設定しています

modules/vpc/outputs.tf
output "private_subnet_id" {
  description = "The ID of the private subnet"
  value       = aws_subnet.private.id
}

modules内のecs配下のvaribles.tfでは、environmentディレクトリからの変数を受け取るための、空の変数を設定しておきます。

modules/ecs/varibles.tf
variable "system_name" {
  type = string
}

variable "environment" {
  type = string
}

variable "private_subnets" {
  type = list(string)
}

ecs配下のmain.tfはこんな感じになります。variables.tfの変数を、リソースの命名や設定に使用しています。

modules/ecs/main.tf
#------------------#
# ECS cluster
#------------------#

resource "aws_ecs_cluster" "ecs_cluster" {
  name = "${var.system_name}-${var.environment}-ecs-cluster" # 変数を使って命名
  setting {
    name  = "containerInsights"
    value = "enabled"
  }
}

#------------------#
# ECS service
#------------------#

resource "aws_ecs_service" "ecs_service_web" {
  name             = "${var.system_name}-${var.environment}-ecs-service-web"
  cluster          = aws_ecs_cluster.ecs_cluster.arn
  launch_type      = "FARGATE"
  desired_count    = 1
  platform_version = "1.4.0"

  network_configuration {
    assign_public_ip = false
    subnets          = var.private_subnets # 変数を経由して設定
  }

  load_balancer {
    container_name   = "sample"
    container_port   = 8080
  }
}

今回はECSリソース情報を使用する他のリソースがないのでoutputs.tfは作成していません。

例えばAWS CodePipeline(以下、CodePipeline)を設定するなどの場合は、呼び出し元でECSのクラスター・サービスを指定する必要があるため、こういった時はoutputs.tfを作成し、CodePipelineのmoduleから参照できるようにしましょう。

呼び出し元の内容

environments内のtest配下のvariables.tfは以下の通りです。moduleに設定している受取用の変数に対して、ここで実際の値を定義している形です。

environments/test/variables.tf
variable "system_name" {
  default = "sample"
}

variable "environment" {
  default = "test"
}

environments内のtest配下のmain.tfは以下の通りです。variables.tfで定義した実際の値を、moduleの変数経由で流し込んでいます。

environments/test/main.tf
module "vpc" {
  source              = "../../modules/vpc"
  system_name         = var.system_name
  environment         = var.environment
  vpc_cidr            = "10.0.0.0/16"
  private_subnet_cidr = "10.0.1.0/24"
}

}

module "ecs" {
  source              = "../../modules/ecs"
  system_name         = var.system_name
  environment         = var.environment
  private_subnets = module.vpc.private_subnet_id # vpcのoutputs.tfで設定した項目
}

このような設計にすることで、moduleをできるだけ再利用可能な状態にできます。これにより個別のリソースの名前や細かいパラメータの設定など、環境によって異なる値をvariableで設定できるので、総コード量も少なくできるというメリットがありますね

プロダクトに合わせた構成を

今回紹介したのは、Terraformを使ったAWSリソース管理でよくやる構成ではありますが、どの粒度で変数に切り出すか、またどの粒度でmoduleを再利用するかは、各プロダクトの要件に依存します。

こちらの構成はあくまで一例ですので、これを元にTerraformの構成を考えてもらえると幸いです。では!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.